/*
	setup_system_IRQs_64.c

	Contains functionality for setting up the system IRQs.
	Sets the legacy PIC to a known state and initialises its spurious interrupt handlers (PIC IRQ 7 + 15).
	Masks all IOAPIC IRQs for now (they are unmasked and routed during specific hardware configuration later).
	Sets LAPIC timer IRQ vector for BSP.
	Sets LAPIC spurious IRQ vector for BSP.
	Sets LAPIC LINT 0 & 1 vectors according to ACPI MADT for BSP.

	NOTE: all IRQs use IST 6 unless otherwise stated with good reason

	Author: Aidan Goddard 17/5/13
*/

#include"../kernel/headers/types.h"
#include"../kernel/headers/system_config_tables.h"
#include"../kernel/headers/ACPI_tables.h"
#include"headers/IRQ.h"
#include"headers/ISR.h"
#include"headers/printf.h"

// IRQ handlers
void IRQ_PIC_spurious_7();
void IRQ_PIC_spurious_15();
void IRQ_APIC_spurious();
void IRQ_APIC_timer();
void CPU_exception_NMI();

/*
// basic routine to set up the HPET for the scheduler's sleep timer until the main driver kicks in
void SetupHPET(uint64_t frequency)
{
	// get base address of system tables
	TBL_SYSCONFIG *config_base = GetSystemConfigBase();

	// now find and configure the HPET
	ACPI_HPET *hpet_t = ACPIGetHPETBase();
	if(hpet_t == NULL)
	{
		printf("\n[IRQINIT] ERROR: HPET table non-existant or invalid");
		__asm__ __volatile__("cli; hlt;");
	}
	volatile uint64_t *hpet = (uint64_t*)hpet_t->base_address;
	printf("\n[IRQINIT] HPET table at 0x%a length %u", hpet_t, hpet_t->length);
	printf("\n[IRQINIT] HPET %u located at 0x%a", hpet_t->HPET_number, hpet_t->base_address);

	// first get features
	__asm__ __volatile__("":::"memory");
	uint64_t features = hpet[0];
	__asm__ __volatile__("":::"memory");
	printf("\n[IRQINIT] HPET features: 0x%x", features);
	uint64_t revision, timer_count, counter_size, legacy_replacement_capable, vendor_id, counter_clock_period;
	revision = features & 0xff;
	timer_count = ((features >> 8) & 0x1f) + 1;
	counter_size = (features >> 13) & 1;
	legacy_replacement_capable = (features >> 15) & 1;
	vendor_id = (features >> 16) & 0xffff;
	counter_clock_period = (features >> 32) & 0xffffffff;
	printf("\n[IRQINIT] HPET revision %u timer count %u counter size %u", revision, timer_count, counter_size);
	printf("\n[IRQINIT] HPET legacy replacement capable %u vendor id %u", legacy_replacement_capable, vendor_id);
	printf("\n[IRQINIT] HPET counter clock period %u femptoseconds", counter_clock_period);

	// check revision is OK
	if(revision == 0)
	{
		printf("\n[IRQINIT] ERROR: HPET revision not valid");
		__asm__ __volatile__("cli; hlt;");
	}

	// calculate number of ticks required
	uint64_t fseconds_for_frequency = 1000000000000000 / frequency;
	uint64_t required_ticks = fseconds_for_frequency / counter_clock_period;
	printf("\n[IRQINIT] HPET required ticks for %uHz = %u", frequency, required_ticks);

	// disable main counter + disable legacy replacement route
	__asm__ __volatile__("":::"memory");
	uint64_t general_config = hpet[2];
	__asm__ __volatile__("":::"memory");
	general_config = general_config & ((uint64_t)~3);
	__asm__ __volatile__("":::"memory");
	hpet[2] = general_config;
	__asm__ __volatile__("":::"memory");

	// zero main counter value register
	__asm__ __volatile__("":::"memory");
	hpet[30] = 0;
	__asm__ __volatile__("":::"memory");

	// find a timer capable of periodic mode
	int periodic_capable_timer = 0;
	int periodic_capable_timer_index = 0;
	int timer_counter = 0;
	for(int i = (0x100 / 8); i < ((0x20 * timer_count) + 0x100) / 8; i += 4)
	{
		// read in value from timer config & capabilities register
		__asm__ __volatile__("":::"memory");
		uint64_t timer_config_cap = hpet[i];
		__asm__ __volatile__("":::"memory");

		// check for periodic support (bit 4 == 1)
		timer_config_cap = (timer_config_cap >> 4) & 1;
		if(timer_config_cap > 0)
		{
			periodic_capable_timer = timer_counter;
			periodic_capable_timer_index = i;
			break;
		}

		// this counter does not support periodic mode
		// move on to next one
		timer_counter++;
	}

	// found periodic capable timer
	__asm__ __volatile__("":::"memory");
	uint64_t periodic_capable_timer_features = hpet[periodic_capable_timer_index];
	__asm__ __volatile__("":::"memory");
	printf("\n[IRQINIT] HPET timer %u (register offset %u) is periodic capable", periodic_capable_timer, periodic_capable_timer_index);
	printf("\n[IRQINIT] HPET timer %u features 0x%a", periodic_capable_timer, periodic_capable_timer_features);

	// check which IRQ delivery method is supported (MSI is prefered, but IOAPIC is also supported)
	uint64_t config_value = 0x4c;		// periodic mode + enable + timer value set
	uint64_t MSI_cap = (periodic_capable_timer_features >> 15) & 1;
	if(MSI_cap > 0)
	{
		// write the address + message value to the FSB delivery register
		uint64_t FSB_address_message = 0;
		uint64_t CPU0_APIC_ID = config_base->tbl_cpu[0].APIC_ID;
		CPU0_APIC_ID = CPU0_APIC_ID & 0xff;
		CPU0_APIC_ID = CPU0_APIC_ID << 12;
		CPU0_APIC_ID = CPU0_APIC_ID + 0xFEE00000;
		CPU0_APIC_ID = CPU0_APIC_ID << 32;
		uint64_t FSB_message = 49;
		FSB_address_message = FSB_message + CPU0_APIC_ID;
		printf("\n[IRQINIT] using MSI IRQ routing with address + message: 0x%a", FSB_address_message);
		__asm__ __volatile__("":::"memory");
		hpet[periodic_capable_timer_index + 2] = FSB_address_message;
		__asm__ __volatile__("":::"memory");

		// set the config to enable the FSB delivery mode
		config_value = config_value | (1 << 14);

	}
	else
	{
		// configure the HPET to use the IOAPIC routing method
		// first find which lines the device is capable of using (note: device MUST NOT be assigned to GSI 1 or 9 - PS2 keyboard and ACPI SCI)
		uint64_t line_bitmap = periodic_capable_timer_features >> 32;
		uint64_t IOAPIC_line = 1; // 1 is a not allowed value so if it defaults to this, there are no valid lines

		// go through the bitmap to find a valid line
		for(int i = 0; i < 32; i++)
		{
			// get the bit for this line
			uint32_t bit = line_bitmap & 1;
			line_bitmap = line_bitmap >> 1;

			// check the current line number for validity
			if(i == 1 || i == 9)
			{
				continue;
			}

			// line number is valid, check the bit
			if(bit == 1)
			{
				// line is available, use it
				IOAPIC_line = i;
				break;
			}

		}

		// check for illegal default line number
		if(IOAPIC_line == 1)
		{
			// line number is illegal. halt on error
			printf("\n[IRQINIT] HPET ERROR: no available legal IOAPIC lines");
			__asm__ __volatile__("cli; hlt;");
		}

		// have legal line number, configure the HPET to use it
		printf("\n[IRQINIT] HPET using IOAPIC IRQ routing to line %u", IOAPIC_line);
		config_value = config_value | (IOAPIC_line << 9);

		// find the IOAPIC this line (line = GSI number) is connected to and configure it for edge-high to vector 49
		uint32_t IOAPIC_ID = config_base->tbl_gsi[IOAPIC_line].IOAPIC_ID;
		uint32_t IOAPIC_input = config_base->tbl_gsi[IOAPIC_line].IOAPIC_input_number;
		uint64_t CPU0_APIC_ID = config_base->tbl_cpu[0].APIC_ID;
		volatile uint32_t *IOAPIC_base = (uint32_t*)(uint64_t)config_base->tbl_ioapic[IOAPIC_ID].IOAPIC_address;
		uint64_t value = (CPU0_APIC_ID << 56) + 49;


		// set IOREGSEL for input number low 32 bits
		__asm__ __volatile__("":::"memory");
		IOAPIC_base[0] = 0x10 + (2 * IOAPIC_input);
		__asm__ __volatile__("":::"memory");

		// send the low 32 bits
		__asm__ __volatile__("":::"memory");
		IOAPIC_base[4] = (uint32_t)value;
		__asm__ __volatile__("":::"memory");

		// set IOREGSEL for input number high 32 bits
		__asm__ __volatile__("":::"memory");
		IOAPIC_base[0] = 0x11 + (2 * IOAPIC_input);
		__asm__ __volatile__("":::"memory");

		// send the high 32 bits
		__asm__ __volatile__("":::"memory");
		IOAPIC_base[4] = (uint32_t)(value >> 32);
		__asm__ __volatile__("":::"memory");

	}

	// write the configuration register
	__asm__ __volatile__("":::"memory");
	hpet[periodic_capable_timer_index] = config_value;
	__asm__ __volatile__("":::"memory");

	// configure the timer for the required frequency
	// write the necessary number of ticks to the comparitor register
	__asm__ __volatile__("":::"memory");
	hpet[periodic_capable_timer_index + 1] = required_ticks;
	__asm__ __volatile__("":::"memory");

	// write the global enable bit
	__asm__ __volatile__("":::"memory");
	general_config = hpet[2];
	__asm__ __volatile__("":::"memory");
	general_config = general_config | 1;
	__asm__ __volatile__("":::"memory");
	hpet[2] = general_config;
	__asm__ __volatile__("":::"memory");

	// finished
	return;
}
*/
// main setup routine
void SetupSystemIRQs()
{
	// print hello
	Printf("\n[IRQINIT] Initialising system IRQs");

	// fill in system configuration tables with information from ACPI MADT
	TBL_SYSCONFIG *config_base = GetSystemConfigBase();

	// get the base address of the MADT
	ACPI_MADT *madt = ACPIGetMADTBase();
	if(madt == NULL)
	{
		printf("\n[IRQINIT] ERROR: MADT table not present or invalid");
		__asm__ __volatile__("cli; hlt;");
	}

	// get pointer to start of entries
	ACPI_MADT_HEAD *headers_base = (ACPI_MADT_HEAD*)((uint64_t)madt + 44);
	ACPI_MADT_HEAD *header = headers_base;

	// start with IOAPICS
	int i = 0;	// iterator into IOAPIC table
	int length = 44;
	while(length < madt->length)
	{
		//printf("\n%u %u", header->type, header->length);

		// check type is IOAPIC
		if(header->type != 1)
		{
			// if not IOAPIC, move onto next one
			length += header->length;
			header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
			continue;
		}

		// type is IOAPIC, grab the information
		ACPI_MADT_IOAPIC *IOAPIC = (ACPI_MADT_IOAPIC*)header;	// get pointer to entry
		config_base->tbl_ioapic[i].IOAPIC_ID = IOAPIC->APIC_ID;
		config_base->tbl_ioapic[i].IOAPIC_address = IOAPIC->address;
		config_base->tbl_ioapic[i].GSI_base = IOAPIC->GSI_base;

		// get the max redirection number (IOAPIC pin count)
		volatile uint32_t *ioregsel = (uint32_t*)(uint64_t)IOAPIC->address;
		volatile uint32_t *ioregwin = (uint32_t*)(uint64_t)(IOAPIC->address + 16);

		__asm__ __volatile__("":::"memory");
		*ioregsel = 1;
		__asm__ __volatile__("":::"memory");
		uint32_t pins = *ioregwin;
		__asm__ __volatile__("":::"memory");

		pins = ((pins >> 16) & 0xff) + 1;

		config_base->tbl_ioapic[i].input_count = pins;

		// set next entry (if it exists) to unused
		if(i < 15)
		{
			config_base->tbl_ioapic[i + 1].IOAPIC_ID = 0xffffffff;
		}

		// finished, move onto next entry
		i++;				// increase interator into IOAPIC table
		length += header->length;
		header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
	}

	// set defaults on ISA -> GSA table
	for(i = 0; i < 16; i ++)	// for each ISA IRQ
	{
		config_base->tbl_isa_gsi[i].GSI = i;	// identity mapped to GSI
		if(i == 2)
		{
			// if ISA IRQ 2 (cascade) set to unknown
			config_base->tbl_isa_gsi[i].GSI = 0xffffffff;
		}

		config_base->tbl_isa_gsi[i].trigger_mode = 1;
		config_base->tbl_isa_gsi[i].polarity = 1;
		config_base->tbl_isa_gsi[i].reserved = 0;
	}

	// then get ISA -> GSA mappings
	header = headers_base;
	length = 44;
	while(length < madt->length)
	{
		// check type is ISA IRQ redirection
		if(header->type != 2)
		{
			// if not, move onto next one
			length += header->length;
			header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
			continue;
		}

		// get irq source
		ACPI_MADT_IRQ *IRQ = (ACPI_MADT_IRQ*)header;
		int source = IRQ->source;

		// set GSI and flags
		config_base->tbl_isa_gsi[source].GSI = IRQ->GSI;

		int trigger_mode = IRQ->flags_triggermode;
		int polarity = IRQ->flags_polarity;

		if(trigger_mode == 0)
		{
			config_base->tbl_isa_gsi[source].trigger_mode = 1;
		}
		else if(trigger_mode == 1)
		{
			config_base->tbl_isa_gsi[source].trigger_mode = 1;
		}
		else
		{
			config_base->tbl_isa_gsi[source].trigger_mode = 0;
		}

		if(polarity == 0)
		{
			config_base->tbl_isa_gsi[source].polarity = 1;
		}
		else if(polarity == 1)
		{
			config_base->tbl_isa_gsi[source].polarity = 1;
		}
		else
		{
			config_base->tbl_isa_gsi[source].polarity = 0;
		}

		// finished, move onto next entry
		length += header->length;
		header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
	}

	// now get GSI mappings
	// set defaults
	for(i = 0; i < 256; i++)	// for each GSI
	{
		config_base->tbl_gsi[i].IOAPIC_ID = 0xffffffff;
		config_base->tbl_gsi[i].type = 0;
		config_base->tbl_gsi[i].PCI_INT = 0;
	}

	// then get details from each IOAPIC
	for(i = 0; i < 16; i++)		// for each IOAPIC
	{
		// check if this entry is marked as unused
		uint32_t IOAPIC_ID = config_base->tbl_ioapic[i].IOAPIC_ID;
		if(IOAPIC_ID == 0xffffffff)
		{
			// unused, so no more IOAPICs
			break;
		}

		// get the GSI base and input count
		int base = config_base->tbl_ioapic[i].GSI_base;
		int limit = base + config_base->tbl_ioapic[i].input_count;

		// for each IOAPIC pin
		int j;
		for(j = base; j < limit; j++)
		{
			// set the IOAPIC ID to the IOAPIC entry number
			config_base->tbl_gsi[j].IOAPIC_ID = i;

			// set the IOAPIC pin number
			config_base->tbl_gsi[j].IOAPIC_input_number = j - base;
		}

	}

	// then get ISA mappings
	for(i = 0; i < 16; i++)		// for each ISA-GSI mapping
	{
		// get the GSI number and flags
		int GSI = config_base->tbl_isa_gsi[i].GSI;
		int trigger_mode = config_base->tbl_isa_gsi[i].trigger_mode;
		int polarity = config_base->tbl_isa_gsi[i].polarity;

		// check the GSI exists
		if(GSI == 0xffffffff)
		{
			continue;
		}

		// set the fields in the GSI table
		config_base->tbl_gsi[GSI].type = 1;	// set type to ISA IRQ
		config_base->tbl_gsi[GSI].ISA_IRQ = i;
		config_base->tbl_gsi[GSI].trigger_mode = trigger_mode;
		config_base->tbl_gsi[GSI].polarity = polarity;
	}

	// then get NMI sources
	header = headers_base;
	length = 44;
	while(length < madt->length)
	{
		// check type is NMI
		if(header->type != 3)
		{
			// if not, move onto next one
			length += header->length;
			header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
			continue;
		}

		// get NMI source
		ACPI_MADT_NMI *NMI = (ACPI_MADT_NMI*)header;

		// get the GSI number and flags
		int GSI = NMI->GSI;

		int trigger_mode = NMI->flags_triggermode;
		int polarity = NMI->flags_polarity;

		if(trigger_mode == 0)
			trigger_mode = 1;
		else if(trigger_mode == 3)
			trigger_mode = 0;

		if(polarity == 0)
			polarity = 1;
		else if(polarity == 3)
			polarity = 0;

		// set them in the GSI table
		config_base->tbl_gsi[GSI].type = 3;
		config_base->tbl_gsi[GSI].trigger_mode = trigger_mode;
		config_base->tbl_gsi[GSI].polarity = polarity;

		//printf("\nNMI");

		// finished, move onto next entry
		length += header->length;
		header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
	}

	// now get CPU configuration information
	// first set defaults
	for(i = 0; i < 32; i++)
	{
		config_base->tbl_cpu[i].enabled = 0;
		config_base->tbl_cpu[i].TSS_descriptor = 0x40 + (0x10 * i);
	}

	// second, get Local APIC descriptors
	header = headers_base;
	length = 44;
	i = 0;
	while(length < madt->length)
	{
		// check type is APIC
		if(header->type != 0)
		{
			// if not, move onto next one
			length += header->length;
			header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
			continue;
		}

		// get APIC entry
		ACPI_MADT_APIC *APIC = (ACPI_MADT_APIC*)header;

		// check if usable
		if((APIC->flags_enabled == 1) && (i < 32))
		{
			// get APIC ID and ACPI CPU ID
			uint32_t APIC_ID = APIC->APIC_ID;
			uint32_t ACPI_ID = APIC->ACPI_CPU_ID;

			// set fields in table
			config_base->tbl_cpu[i].enabled = 1;
			config_base->tbl_cpu[i].APIC_ID = APIC_ID;
			config_base->tbl_cpu[i].ACPI_CPU_ID = ACPI_ID;
			config_base->tbl_cpu[i].LINT0_type = 0;
			config_base->tbl_cpu[i].LINT1_type = 0;
			i++;
		}

		// finished, move onto next entry
		length += header->length;
		header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
	}

	// then get Local NMI sources
	header = headers_base;
	length = 44;
	while(length < madt->length)
	{
		// check type is L-NMI
		if(header->type != 4)
		{
			// if not, move onto next one
			length += header->length;
			header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
			continue;
		}

		// get L-NMI entry
		ACPI_MADT_LNMI *LNMI = (ACPI_MADT_LNMI*)header;

		// get ACPI CPU ID, flags and LINTn
		uint32_t ACPI_CPU_ID = LNMI->ACPI_CPU_ID;
		uint32_t LINTn = LNMI->LINTn;
		int polarity = LNMI->flags_polarity;
		int trigger_mode = LNMI->flags_triggermode;

		if(trigger_mode == 0)
			trigger_mode = 1;
		else if(trigger_mode == 3)
			trigger_mode = 0;

		if(polarity == 0)
			polarity = 1;
		else if(polarity == 3)
			polarity = 0;

		// check if sending to all CPUs or not
		if(LNMI->ACPI_CPU_ID == 0xff)
		{
			// send to all 32 CPUs
			for(i = 0; i < 32; i++)
			{
				if(LINTn == 0)
				{
					config_base->tbl_cpu[i].LINT0_type = 1;
					config_base->tbl_cpu[i].LINT0_polarity = polarity;
					config_base->tbl_cpu[i].LINT0_triggermode = trigger_mode;
				}
				else
				{
					config_base->tbl_cpu[i].LINT1_type = 1;
					config_base->tbl_cpu[i].LINT1_polarity = polarity;
					config_base->tbl_cpu[i].LINT1_triggermode = trigger_mode;
				}
			}

		}
		else
		{
			// send to specific CPU
			// find the CPU with the given ACPI_CPU_ID
			for(i = 0; i < 32; i++)
			{
				if(config_base->tbl_cpu[i].ACPI_CPU_ID != ACPI_CPU_ID)
				{
					continue;
				}

				if(LINTn == 0)
				{
					config_base->tbl_cpu[i].LINT0_type = 1;
					config_base->tbl_cpu[i].LINT0_polarity = polarity;
					config_base->tbl_cpu[i].LINT0_triggermode = trigger_mode;
				}
				else
				{
					config_base->tbl_cpu[i].LINT1_type = 1;
					config_base->tbl_cpu[i].LINT1_polarity = polarity;
					config_base->tbl_cpu[i].LINT1_triggermode = trigger_mode;
				}
				break;
			}
		}

		// finished, move onto next entry
		length += header->length;
		header = (ACPI_MADT_HEAD*)((uint64_t)header + (uint64_t)header->length);
	}




	// print out CPU information
	for(i = 0; i < 32; i++)
	{
		if(config_base->tbl_cpu[i].enabled == 1)
		{
			printf("\n[IRQINIT] CPU %u ", i);
			printf("ENABLED with APIC %u, ACPI ID %u, LINT0 %u, LINT1 %u TSS 0x%x", config_base->tbl_cpu[i].APIC_ID, config_base->tbl_cpu[i].ACPI_CPU_ID, config_base->tbl_cpu[i].LINT0_type, config_base->tbl_cpu[i].LINT1_type, config_base->tbl_cpu[i].TSS_descriptor);
		}
	}


	// print out IRQ information
	for(i = 0; i < 256; i++)
	{
		if(config_base->tbl_gsi[i].IOAPIC_ID == 0xffffffff)
		{
			break;
		}

		printf("\n[IRQINIT] GSI %u -> ", i);
		int trigger_mode = config_base->tbl_gsi[i].trigger_mode;
		int polarity = config_base->tbl_gsi[i].polarity;

		if(config_base->tbl_gsi[i].type == 0)
		{
			printf("*unused*");
			continue;
		}
		else if(config_base->tbl_gsi[i].type == 1)
		{
			printf("ISA IRQ %u %u %u", config_base->tbl_gsi[i].ISA_IRQ, trigger_mode, polarity);
		}
		else if(config_base->tbl_gsi[i].type == 2)
		{
			printf("PCI IRQ INT %u flags: %u %u", config_base->tbl_gsi[i].PCI_INT, trigger_mode, polarity);
		}
		else
		{
			printf("NMI");
		}
	}
	/*
	*/



	/*
		Now configure the hardware
	*/

	// start by enabling the PIC and mapping it to 32 - 47 (all IRQs in the PIC are masked during setup)
	PICSetup(32);

	// set this CPU (CPU 0)'s APIC timer to vector 50
	// first get APIC base address
	uint32_t apic_low, apic_high;
	rdmsr(0x1b, &apic_low, &apic_high);		// get msr contents
	apic_low = apic_low | 0x800;			// set hardware enable bit
	wrmsr(0x1b, apic_low, apic_high);		// write msr contents
	volatile uint32_t *APIC_base = (uint32_t*)(uint64_t)(apic_low & 0xfffff000);

	// set APIC timer vector register (unmasked, continuous mode to vector 50)
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x320/4)) = (50 + (1 << 17));
	__asm__ __volatile__("":::"memory");

	// set the APIC timer divide register to 16 (seems an appropriate number)
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x3e0/4)) = 3;
	__asm__ __volatile__("":::"memory");

	// set the APIC timer initial value to one that represents 10ms (ish)
	uint32_t timer_value = config_base->tbl_stuff.APIC_ticks_sec;		// get ticks per second
	timer_value = timer_value / 16;						// calculate number of ticks required per second taking into account the divide register
	timer_value = timer_value / 100;					// divide by 100 as we want every 10ms -> 100Hz
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x380/4)) = timer_value;
	__asm__ __volatile__("":::"memory");

	// set this CPU's APIC spurious interrupt to vector 63
	__asm__ __volatile__("":::"memory");
	uint32_t APIC_spurious = *(APIC_base + (0xf0/4));	// get contents of spurious interrupt register
	__asm__ __volatile__("":::"memory");
	APIC_spurious = APIC_spurious | 0x100;		// set software enable bit
	APIC_spurious = APIC_spurious & 0xffffff00;	// clear vector
	APIC_spurious = APIC_spurious + 63;		// set vector to 0x3f (63)
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0xf0/4)) = APIC_spurious;	// set new contents of spurious interrupt register
	__asm__ __volatile__("":::"memory");

	// check if this CPU has any LINTn configurations
	// BSP is always CPU 0 (first entry in ACPI MADT is always the BSP)
	// is LINT0 an NMI?
	if(config_base->tbl_cpu[0].LINT0_type == 1)
	{
		// get trigger mode
		uint32_t trigger_mode = config_base->tbl_cpu[0].LINT0_triggermode;
		if(trigger_mode == 0)
			trigger_mode = 1;
		else
			trigger_mode = 0;

		// set value to write to register (trigger mode + type)
		uint32_t value = (trigger_mode << 15);
		value = value + (1 << 10);

		// write it to the register
		__asm__ __volatile__("":::"memory");
		*(APIC_base + (0x350/4)) = value;
		__asm__ __volatile__("":::"memory");
		printf("\n[IRQINIT] LINT 0 NMI 0x%x", value);
	}
	else
	{
		// mask it
		uint32_t value = (1 << 16);
		__asm__ __volatile__("":::"memory");
		*(APIC_base + (0x350/4)) = value;
		__asm__ __volatile__("":::"memory");
		printf("\n[IRQINIT] LINT 0 masked 0x%x", value);
	}

	// is LINT1 an NMI?
	if(config_base->tbl_cpu[0].LINT1_type == 1)
	{
		// get trigger mode
		uint32_t trigger_mode = config_base->tbl_cpu[0].LINT1_triggermode;
		if(trigger_mode == 0)
			trigger_mode = 1;
		else
			trigger_mode = 0;

		// set value to write to register (trigger mode + type)
		uint32_t value = (trigger_mode << 15);
		value = value + (1 << 10);

		// write it to the register
		__asm__ __volatile__("":::"memory");
		*(APIC_base + (0x360/4)) = value;
		__asm__ __volatile__("":::"memory");
		printf("\n[IRQINIT] LINT 1 NMI 0x%x", value);
	}
	else
	{
		// mask it
		uint32_t value = (1 << 16);
		__asm__ __volatile__("":::"memory");
		*(APIC_base + (0x360/4)) = value;
		__asm__ __volatile__("":::"memory");
		printf("\n[IRQINIT] LINT 1 masked 0x%x", value);
	}

	// check if any GSIs are NMI and map them
	for(i = 0; i < 256; i++)
	{
		// check type
		if(config_base->tbl_gsi[i].type != 3)
		{
			// not NMI, move on
			continue;
		}

		// is NMI
		uint32_t trigger_mode = config_base->tbl_gsi[i].trigger_mode;
		uint32_t polarity = config_base->tbl_gsi[i].polarity;

		if(trigger_mode == 0)
			trigger_mode = 1;
		else
			trigger_mode = 0;

		if(polarity == 0)
			polarity = 1;
		else
			polarity = 0;

		// generate value to write to IOAPIC
		uint32_t value_low = (1 << 10);
		value_low = value_low + (polarity << 14);
		value_low = value_low + (trigger_mode << 15);

		// send it to the BSP
		uint32_t value_high = (config_base->tbl_cpu[0].APIC_ID << 24);

		// get the IOAPIC address
		uint32_t e = config_base->tbl_gsi[i].IOAPIC_ID;
		volatile uint32_t *IOAPIC_BASE = (uint32_t*)(uint64_t)config_base->tbl_ioapic[e].IOAPIC_address;

		// get the register numbers for this entry number
		uint32_t register_lo = (config_base->tbl_gsi[i].IOAPIC_input_number * 2) + 0x10;
		uint32_t register_hi = (config_base->tbl_gsi[i].IOAPIC_input_number * 2) + 0x11;

		// set IOREGSEL for low write
		__asm__ __volatile__("":::"memory");
		*IOAPIC_BASE = register_lo;
		__asm__ __volatile__("":::"memory");

		// write the low value
		__asm__ __volatile__("":::"memory");
		*(IOAPIC_BASE + (0x10/4)) = value_low;
		__asm__ __volatile__("":::"memory");

		// set IOREGSEL for high write
		__asm__ __volatile__("":::"memory");
		*IOAPIC_BASE = register_hi;
		__asm__ __volatile__("":::"memory");

		// write the high value
		__asm__ __volatile__("":::"memory");
		*(IOAPIC_BASE + (0x10/4)) = value_high;
		__asm__ __volatile__("":::"memory");

		// print info
		printf("\n[IRQINIT] NMI on GSI %u", i);
		printf("\n          reg_lo 0x%x", register_lo);
		printf("\n          reg_hi 0x%x", register_hi);
		printf("\n          value_low  0x%x", value_low);
		printf("\n          value_high 0x%x", value_high);
	}

	// set the system build date and number
	config_base->tbl_stuff.build_date = __BUILD_DATE;
	config_base->tbl_stuff.build_number = __BUILD_NUMBER;

	// finished
	return;
}

